/**
 * Javada - Copyright 2009  Rapita Systems Ltd.
 * http://www.rapitasystems.com  javada@rapitasystems.com
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.rapitasystems.javada;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import net.sf.eclipsecorba.idl.Definition;
import net.sf.eclipsecorba.idl.DefinitionContainer;
import net.sf.eclipsecorba.idl.Export;
import net.sf.eclipsecorba.idl.Identifiable;
import net.sf.eclipsecorba.idl.IdlInterfaceDcl;
import net.sf.eclipsecorba.idl.IdlType;
import net.sf.eclipsecorba.idl.IdlTypeDcl;
import net.sf.eclipsecorba.idl.Member;
import net.sf.eclipsecorba.idl.Module;
import net.sf.eclipsecorba.idl.Specification;
import net.sf.eclipsecorba.idl.Typed;
import net.sf.eclipsecorba.idl.operations.Operation;
import net.sf.eclipsecorba.idl.operations.Parameter;
import net.sf.eclipsecorba.idl.types.Boolean;
import net.sf.eclipsecorba.idl.types.EnumType;
import net.sf.eclipsecorba.idl.types.IdlChar;
import net.sf.eclipsecorba.idl.types.IdlString;
import net.sf.eclipsecorba.idl.types.Long;
import net.sf.eclipsecorba.idl.types.LongDouble;
import net.sf.eclipsecorba.idl.types.LongLong;
import net.sf.eclipsecorba.idl.types.Octet;
import net.sf.eclipsecorba.idl.types.PrimitiveType;
import net.sf.eclipsecorba.idl.types.SequenceType;
import net.sf.eclipsecorba.idl.types.Short;
import net.sf.eclipsecorba.idl.types.StructType;
import net.sf.eclipsecorba.idl.types.TypeDef;
import net.sf.eclipsecorba.idl.types.ULong;
import net.sf.eclipsecorba.idl.types.ULongLong;
import net.sf.eclipsecorba.idl.types.UShort;
import net.sf.eclipsecorba.idl.types.VoidType;
import net.sf.eclipsecorba.idl.types.WChar;
import net.sf.eclipsecorba.idl.types.WString;

import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject;

import com.rapitasystems.javada.Options.ArgumentException;
import com.rapitasystems.javada.templates.AdaModuleBody;
import com.rapitasystems.javada.templates.AdaModuleUtilBody;
import com.rapitasystems.javada.templates.AdaModuleUtilSpec;
import com.rapitasystems.javada.templates.AdaModuleSpec;
import com.rapitasystems.javada.templates.Banner;
import com.rapitasystems.javada.templates.JavaEnum;
import com.rapitasystems.javada.templates.JavaImpl;
import com.rapitasystems.javada.templates.JavaInterface;
import com.rapitasystems.javada.templates.JavaStruct;
import com.rapitasystems.javada.templates.Usage;

public class Generator {
	
	private static Options options;
	private static HashSet<String> adaReservedWordsSet;
	
	private static final String[] adaReservedWords = new String[] {
		"loop",
		"for",
		"is",
		"package",
		"procedure",
		"function",
		"returns",
		"exception",
		"use",
		"end",
		"type",
		"J_Obj"
	};
	
	
	
	public static void main(String[] args) {
		printBanner();
		try {
			options = new Options(args);
		} catch (ArgumentException e) {
			System.err.println(e.getMessage());
			printUsage();
			System.exit(-1);
		}
		IdlParser parser = new IdlParser();
		Specification spec = parser.parseIDLFile(options.getInputFile(), options.getIncludeDirs());
		process(spec);
	}

	private static void printBanner() {
		System.out.println(new Banner().generate(null));
	}

	private static void printUsage() {
		System.err.print(new Usage().generate(null));
	}

	private static void writeIfNotExisting(String content, File file) {
		if (!file.exists())
			write(content, file);
	}
	
	public static Options getOptions() {
		return options;
	}
	
	/**
	 * Gather list of types that need to be imported (in java)
	 * @param def
	 * @return
	 */
	public static Set<Definition> gatherTypes(Definition def) {
		return gatherTypes(def, new HashSet<Definition>());
	}
	
	public static Set<Definition> gatherTypes(Definition def, HashSet<Definition> visited) {
		Set<Definition> types = new HashSet<Definition>();
		// Ensure we don't follow same branch twice
		if (visited.contains(def))
			return types;
		else
			visited.add(def);
		
		if (def instanceof TypeDef) {
			checkDefinition(types, ((TypeDef)def).getType());
		}
		else if (def instanceof IdlInterfaceDcl) {
			for (Export e: (List<Export>)((IdlInterfaceDcl)def).getBody()) {
				checkDefinition(types, e.getType());
				if (e instanceof Operation)
					for (Parameter p: (List<Parameter>)((Operation)e).getParameters()) {
						checkDefinition(types, p.getType());
					}
			}
			for (IdlInterfaceDcl dcl: (List<IdlInterfaceDcl>)((IdlInterfaceDcl)def).getInheritedInterfaces()) {
				checkDefinition(types, dcl);
				types.addAll(gatherTypes(dcl, visited));
			}
		}
		else if (def instanceof StructType) {
			for (Member m: (List<Member>)((StructType)def).getMembers()) {
				checkDefinition(types, m.getType());
			}
		}
		else if (def instanceof Module) {
			for (Definition d: (List<Definition>)((Module)def).getDefinitions()) {
				checkDefinition(types, d);
				types.addAll(gatherTypes(d, visited));
			}
		}
		types.remove(def);
		return types;
	}
	
	public static Set<Definition> gatherPackages(Definition def) {
		Set<Definition> packages = new HashSet<Definition>();
		Set<Definition> types = gatherTypes(def);
		for (Definition d: types) {
			checkDefinition(packages, getModule(d));
		}
		
//		if (def instanceof TypeDef) {
//			checkDefinition(types, getModule(((TypeDef)def).getType()));
//		}
//		else if (def instanceof Module) {
//			for (Definition d: (List<Definition>)((Module)def).getDefinitions()) {
//				checkDefinition(types, getModule(d));
//				types.addAll(gatherPackages(d));
//			}
//		}
//		else if (def instanceof IdlInterfaceDcl) {
//			for (Export e: (List<Export>)((IdlInterfaceDcl)def).getBody()) {
//				checkDefinition(types, getModule(e.getType()));
//				if (e instanceof Operation)
//					for (Parameter p: (List<Parameter>)((Operation)e).getParameters()) {
//						checkDefinition(types, getModule(p.getType()));
//					}
//			}
//		}
//		else if (def instanceof StructType) {
//			for (Member m: (List<Member>)((StructType)def).getMembers()) {
//				checkDefinition(types, getModule(m.getType()));
//			}
//		}
		packages.remove(def);
		return packages;
	}

	private static Module getModule(EObject d) {
		if (d == null)
			return null;
		else if (d instanceof Module)
			return (Module) d;
		else
			return getModule(d.eContainer());
	}

	private static void checkDefinition(Set<Definition> types, Object t) {
		if (t instanceof SequenceType)
			checkDefinition(types, ((SequenceType)t).getType());
//		else if (t instanceof TypeDef)
//			checkDefinition(types, ((TypeDef)t).getType());
		else if (!(t instanceof Definition))
			return;
		else
			types.add((Definition) t);
	}

	private static void process(DefinitionContainer spec) {
		for (Definition def: (List<Definition>) spec.getDefinitions()) {
			if (def instanceof Module) {
				write(new AdaModuleSpec().generate(def), new File(getAdaFileName(def) + ".ads"));
				if (needsBody((Module) def))
					write(new AdaModuleBody().generate(def), new File(getAdaFileName(def) + ".adb"));
				write(new AdaModuleUtilSpec().generate(def), new File(getAdaFileName(def) + "-util.ads"));
				write(new AdaModuleUtilBody().generate(def), new File(getAdaFileName(def) + "-util.adb"));
				process((DefinitionContainer) def);
			}
			else if (def instanceof IdlInterfaceDcl) {
				// Ada templates
//				String adaFileName = getAdaFileName(def);
//				write(new AdaInterfaceSpec().generate(def), new File(adaFileName + ".ads"));
//				write(new AdaInterfaceBody().generate(def), new File(adaFileName + ".adb"));
//				write(new AdaUtilSpec().generate(def), new File(adaFileName + "-util.ads"));
//				write(new AdaUtilBody().generate(def), new File(adaFileName + "-util.adb"));
				
				// Java templates
				write(new JavaInterface().generate(def), new File(getJavaFileName(def)));
				if (!((IdlInterfaceDcl)def).isAbstract())
					write(new JavaImpl().generate(def), new File(getJavaImplFileName(def)));
				
				
			}
			else if (def instanceof StructType) {
				// Ada templates
//				write(new AdaStructSpec().generate(def), new File(getAdaFileName(def) + ".ads"));
//				write(new AdaStructBody().generate(def), new File(getAdaFileName(def) + ".adb"));
//				write(new AdaStructUtilSpec().generate(def), new File(getAdaFileName(def) + "-util.ads"));
//				write(new AdaStructUtilBody().generate(def), new File(getAdaFileName(def) + "-util.adb"));
				
				// Java templates
				write(new JavaStruct().generate(def), new File(getJavaFileName(def)));
			}
			else if (def instanceof EnumType) {
				// Java template
				write(new JavaEnum().generate(def), new File(getJavaFileName(def)));
			}
		}
	}

	private static boolean needsBody(Module module) {
		for (Definition def: (List<Definition>) module.getDefinitions()) {
			if (def instanceof StructType)
				return true;
			else if (def instanceof IdlInterfaceDcl &&
					!((IdlInterfaceDcl)def).isAbstract())
				return true;
		}
		return false;
	}

	private static String getJavaImplFileName(Definition def) {
		return options.getOutputPath() + getJavaImplPackage(def).replace('.', '/') + "/" + def.getName() + "Prx.java";
	}

	private static String getJavaFileName(Definition def) {
		return options.getOutputPath() + getJavaPackage(def).replace('.', '/') + "/" + def.getName() + ".java";
	}

	private static void write(String content, File file) {
		try {
			System.out.println("Writing " + file.getPath());
			file.getParentFile().mkdirs();
			PrintWriter printWriter = new PrintWriter(new FileOutputStream(file));
			printWriter.print(content);
			printWriter.flush();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
	}

	private static String getAdaFileName(Definition def) {
		return options.getOutputPath() + getAdaPackage(def).replace('.', '-').toLowerCase();
	}

	public static String getJavaName(Identifiable id) {
		return id.getName();
	}

	public static String getAdaName(Identifiable id) {
		String name = id.getName();
//		if (isAdaReserved(name))
//			name = "X_" + name;
		return name.substring(0, 1).toUpperCase() + name.substring(1);
	}

	private static boolean isAdaReserved(String name) {
		if (adaReservedWordsSet == null) {
			adaReservedWordsSet = new HashSet<String>();
			for (String s: adaReservedWords)
				adaReservedWordsSet.add(s.toLowerCase());
		}
		return adaReservedWordsSet.contains(name.toLowerCase());
	}

	public static String generateJavaPackage(Definition def) {
		EObject parent = def.eContainer();
		if (parent instanceof Module) {
			String parentPackage = generateJavaPackage((Module) parent);
			return (parentPackage == null ? "" : parentPackage + ".") + getJavaName((Module)parent);
		}
		else
			return null;
	}
	
	public static String getJavaPackage(Definition def) {
		return (options.getJavaPrefix() == null ? "" : options.getJavaPrefix() + ".") + generateJavaPackage(def);
	}

	public static String getAdaPackage(Definition def) {
		EObject parent = def.eContainer();
		if (parent instanceof Module) {
			return getAdaPackage((Module) parent) + "." + getAdaName(def);
		}
		else
			return getAdaName(def);
	}

	public static boolean isVoidFunction(Operation o) {
		return o.getType() instanceof VoidType;
	}

	public static String getJNIReturnType(Operation o) {
		return getJNIType(o.getType());
	}

	public static String getJNIType(Typed type) {
		return getJNIType(type.getType());
	}
	
	public static String getJNIType(IdlType type) {
		if (type == null)
			return "<null type>";
		else if (type instanceof VoidType)
			return "Void";
		
		// Typedef
		else if (type instanceof TypeDef) {
			return getJNIType(((TypeDef)type).getType());
		}
		
		// Named types
//		else if (type instanceof EnumType) {
//			return getAdaType((IdlType)type);
//		}
		else if (type instanceof IdlTypeDcl) {
			return getJNIName((IdlTypeDcl)type, null);
		}
		
		// Base types -------------------------
//		else if (type instanceof PrimativeType) {
//			return getAdaType(type);
//		}
		else if (type instanceof Boolean) {
			return "jboolean";
		}
		else if (type instanceof IdlChar) {
			return "jchar";
		}
		else if (type instanceof net.sf.eclipsecorba.idl.types.Float) {
			return "jfloat";
		}
		else if (type instanceof net.sf.eclipsecorba.idl.types.Double) {
			return "jdouble";
		}
		else if (type instanceof LongDouble) {
			return "jdouble";
		}
		else if (type instanceof Long) {
			return "jint";
		}
		else if (type instanceof LongLong) {
			return "jlong";
		}
		else if (type instanceof Octet) {
			return "jbyte";
		}
		else if (type instanceof Short) {
			return "jshort";
		}
		else if (type instanceof ULong) {
			return "jint";
		}
		else if (type instanceof ULongLong) {
			return "jlong";
		}
		else if (type instanceof UShort) {
			return "jchar";
		}
		else if (type instanceof WChar) {
			return "jchar";
		}
		
		else if (type instanceof IdlString) {
			return "jstring";
		}
		else if (type instanceof WString) {
			return "jstring";
		}
		
		// Sequences
		else if (type instanceof SequenceType) {
			return getJNIType(((SequenceType)type).getType()) + "_Array";
		}
		
		// Unrecognised -----------------------
		else
			return "<unrecognised type: " + type.getClass().getSimpleName() + ">";
	}
	
	public static String getJNIName(Identifiable type, Definition context) {
		String s;
		if (type instanceof Operation)
			s = "J_" + getAdaName(context) + "_" + getAdaName(type);
		else
			s = "J_" + getAdaName(type);
		
		if (isAdaReserved(s))
			return "X_" + s;
		else
			return s;
	}

	public static String getAdaType(Typed type) {
		return getAdaType(type.getType());
	}
	
	public static String getAdaType(IdlType type) {
		return getAdaType(type, false);
	}
	
	public static String getAdaType(IdlType type, boolean classwide) {
		if (type == null)
			return "<null type>";
		else if (type instanceof VoidType)
			return "<void type>";
		
		// Typedef
		else if (type instanceof TypeDef) {
			if (((TypeDef)type).getType() instanceof SequenceType)
				return getAdaType(((TypeDef)type).getType(), classwide);
			else
				return getAdaName((TypeDef) type);
		}
		
		// Named types
		else if (type instanceof EnumType) {
			return getAdaName((IdlTypeDcl)type);
		}
		else if (type instanceof StructType) {
			return getAdaName((StructType)type);
		}
		else if (type instanceof IdlTypeDcl) {
			return options.getTypePrefix() + getAdaName((IdlTypeDcl)type) + 
			(classwide ? "'Class" : "");
		}
		
		// Base types -------------------------
		else if (type instanceof Boolean) {
			return "Standard.Boolean";
		}
		else if (type instanceof IdlChar) {
			return "Character";
		}
		else if (type instanceof net.sf.eclipsecorba.idl.types.Float) {
			return "Float";
		}
		else if (type instanceof net.sf.eclipsecorba.idl.types.Double) {
			return "Double";
		}
		else if (type instanceof LongDouble) {
			return "Double";
		}
		else if (type instanceof Long) {
			return "Integer";
		}
		else if (type instanceof LongLong) {
			return "Long_Integer";
		}
		else if (type instanceof Octet) {
			return "Byte";
		}
		else if (type instanceof Short) {
			return "Short_Integer";
		}
		else if (type instanceof ULong) {
			return "Integer";
		}
		else if (type instanceof ULongLong) {
			return "Long_Integer";
		}
		else if (type instanceof UShort) {
			return "Short_Integer";
		}
		else if (type instanceof WChar) {
			return "Wide_Character";
		}
		
		else if (type instanceof IdlString) {
			return "String";
		}
		else if (type instanceof WString) {
			return "Wide_String";
		}
		
		// Sequences
		else if (type instanceof SequenceType) {
			SequenceType sequence = (SequenceType)type;
//			return "array (Integer range <>) of " + getAdaType((Typed)sequence);
			return getAdaType(sequence.getType()) + options.getArraySuffix() + 
			(classwide ? "'Class" : "");
		}
		
		// Unrecognised -----------------------
		else
			return "<unrecognised type: " + type.getClass().getSimpleName() + ">";
	}

	public static String getJavaImplClass(Definition def) {
		return getJavaImplPackage(def) + "." + getJavaName(def) + options.getPrxSuffix();
	}
	
	public static String getJavaClass(Definition def) {
		return getJavaPackage(def) + "." + getJavaName(def);
	}
	
	public static String getMangledJavaName(Definition context, Object o) {
		if (o instanceof IdlInterfaceDcl)
			return "Java_" + getMangledJavaName(null, getJavaImplClass((Definition) o)) ;
		else if (o instanceof Operation) {
			Operation op = (Operation)o;
			return getMangledJavaName(null, context) + "_" + getMangledJavaName(null, op.getName());
		}
		else if (o instanceof String)
			return ((String)o).replace("_", "_1").replace('.', '_');
		else
			return "";
	}

	public static String getJavaSignature(Operation o) {
		StringBuffer sig = new StringBuffer("(");
		for (Parameter p: (List<Parameter>)o.getParameters()) {
			sig.append(getJavaSignature(p.getType()));
		}
		sig.append(")" + getJavaSignature(o.getType()));
		return sig.toString();
	}

	public static String getJavaSignature(IdlType type) {
		if (type == null)
			return "<null type>";
		else if (type instanceof VoidType)
			return "V";
		
		// Typedef
		else if (type instanceof TypeDef) {
			return getJavaSignature(((TypeDef)type).getType());
		}
		
		// Named types
		else if (type instanceof IdlTypeDcl) {
			IdlTypeDcl namedType = (IdlTypeDcl)type;
			return "L" + getJavaClass(namedType).replace('.', '/') + ";";
		}
		
		// Base types -------------------------
		else if (type instanceof Boolean) {
			return "Z";
		}
		else if (type instanceof IdlChar) {
			return "C";
		}
		else if (type instanceof net.sf.eclipsecorba.idl.types.Float) {
			return "F";
		}
		else if (type instanceof net.sf.eclipsecorba.idl.types.Double) {
			return "D";
		}
		else if (type instanceof LongDouble) {
			return "D";
		}
		else if (type instanceof Long) {
			return "I";
		}
		else if (type instanceof LongLong) {
			return "J";
		}
		else if (type instanceof Octet) {
			return "B";
		}
		else if (type instanceof Short) {
			return "S";
		}
		else if (type instanceof ULong) {
			return "I";
		}
		else if (type instanceof ULongLong) {
			return "J";
		}
		else if (type instanceof UShort) {
			return "S";
		}
		else if (type instanceof WChar) {
			return "C";
		}
		
		else if (type instanceof IdlString) {
			return "Ljava/lang/String;";
		}
		else if (type instanceof WString) {
			return "Ljava/lang/String;";
		}
		
		// Sequences
		else if (type instanceof SequenceType) {
			SequenceType sequence = (SequenceType)type;
			return "[" + getJavaSignature(sequence.getType());
		}
		
		// Unrecognised -----------------------
		else
			return "<unrecognised type: " + type.getClass().getSimpleName() + ">";
	}

	public static String getJNIBaseType(Typed type) {
		return getJNIBaseType(type.getType());
	}
	
	public static String getJNIBaseType(IdlType type) {
		if (type == null)
			return "<null type>";
		else if (type instanceof VoidType)
			return "Void";
		
		// Typedef
		else if (type instanceof TypeDef) {
			return getJNIBaseType(((TypeDef)type).getType());
		}
		
		// Named types
		else if (type instanceof IdlTypeDcl) {
			return "Object";
		}
		
		// Base types -------------------------
		else if (type instanceof Boolean) {
			return "Boolean";
		}
		else if (type instanceof IdlChar) {
			return "Char";
		}
		else if (type instanceof net.sf.eclipsecorba.idl.types.Float) {
			return "Float";
		}
		else if (type instanceof net.sf.eclipsecorba.idl.types.Double) {
			return "Double";
		}
		else if (type instanceof LongDouble) {
			return "Double";
		}
		else if (type instanceof Long) {
			return "Int";
		}
		else if (type instanceof LongLong) {
			return "Long";
		}
		else if (type instanceof Octet) {
			return "Byte";
		}
		else if (type instanceof Short) {
			return "Short";
		}
		else if (type instanceof ULong) {
			return "Int";
		}
		else if (type instanceof ULongLong) {
			return "Long";
		}
		else if (type instanceof UShort) {
			return "Short";
		}
		else if (type instanceof WChar) {
			return "Char";
		}
		
		else if (type instanceof IdlString) {
			return "Object";
		}
		else if (type instanceof WString) {
			return "Object";
		}
		
		// Sequences
		else if (type instanceof SequenceType) {
			return "Object";
		}
		
		// Unrecognised -----------------------
		else
			return "<unrecognised type: " + type.getClass().getSimpleName() + ">";
	}

	public static String getJavaType(Typed type) {
		return getJavaType(type.getType());
	}

	public static String getJavaType(IdlType type) {
		if (type == null)
			return "<null type>";
		else if (type instanceof VoidType)
			return "void";
		
		// Typedef
		else if (type instanceof TypeDef) {
			return getJavaType(((TypeDef)type).getType());
		}
		
		// Named types
		else if (type instanceof IdlTypeDcl) {
			return ((IdlTypeDcl)type).getName();
		}
		
		// Base types -------------------------
		else if (type instanceof Boolean) {
			return "boolean";
		}
		else if (type instanceof IdlChar) {
			return "char";
		}
		else if (type instanceof net.sf.eclipsecorba.idl.types.Float) {
			return "float";
		}
		else if (type instanceof net.sf.eclipsecorba.idl.types.Double) {
			return "double";
		}
		else if (type instanceof LongDouble) {
			return "double";
		}
		else if (type instanceof Long) {
			return "int";
		}
		else if (type instanceof LongLong) {
			return "long";
		}
		else if (type instanceof Octet) {
			return "byte";
		}
		else if (type instanceof Short) {
			return "short";
		}
		else if (type instanceof ULong) {
			return "int";
		}
		else if (type instanceof ULongLong) {
			return "long";
		}
		else if (type instanceof UShort) {
			return "short";
		}
		else if (type instanceof WChar) {
			return "char";
		}
		
		else if (type instanceof IdlString) {
			return "String";
		}
		else if (type instanceof WString) {
			return "String";
		}
		
		// Sequences
		else if (type instanceof SequenceType) {
			return getJavaType(((SequenceType)type).getType()) + "[]";
		}
		
		// Unrecognised -----------------------
		else
			return "<unrecognised type: " + type.getClass().getSimpleName() + ">";
	}

	public static String getJavaImplPackage(Definition def) {
		return getJavaPackage(def) + "." + options.getPrxSuffix().toLowerCase();
	}

	public static String getLibraryName() {
		return options.getLibrayName();
	}

	public static boolean isPrimitiveType(IdlType type) {
		return isPrimitiveType(type, false);
	}
	
	public static boolean isPrimitiveType(IdlType type, boolean transitive) {
		boolean primitive;
		if (transitive && type instanceof TypeDef)
			primitive = isPrimitiveType(((TypeDef)type).getType(), transitive);
		else
			primitive = type instanceof PrimitiveType && !(type instanceof Boolean) && !(type instanceof IdlString);
		return primitive;
	}

	public static Set<Operation> gatherOperations(IdlInterfaceDcl iface) {
		return gatherOperations(iface, new HashSet<IdlInterfaceDcl>());
	}

	private static Set<Operation> gatherOperations(IdlInterfaceDcl iface,
			HashSet<IdlInterfaceDcl> visited) {
		HashSet<Operation> operations = new HashSet<Operation>();
		if (visited.contains(iface))
			return operations;
		else
			visited.add(iface);
		
		for (IdlInterfaceDcl dcl : (List<IdlInterfaceDcl>)iface.getInheritedInterfaces()) {
			operations.addAll(gatherOperations(dcl, visited));
		}
		for (Export e: (List<Export>)iface.getBody()) {
			if (e instanceof Operation)
				operations.add((Operation) e);
		}
		return operations;
	}

	public static List<Definition> sortDefinitions(EList definitions) {
		ArrayList<Definition> defs = new ArrayList<Definition>();
		loop: for (Definition d: (List<Definition>)definitions) {
			if (d instanceof IdlInterfaceDcl) {
				for (Definition d2 : defs) {
					if (d2 instanceof IdlInterfaceDcl) {
						if (((IdlInterfaceDcl)d2).getInheritedInterfaces().contains(d)) {
							// Insert before dependent element
							defs.add(defs.indexOf(d2), d);
							continue loop;
						}
					}
				}
			}
			defs.add(d);
		}
		return defs;
	}

	public static String getConversionPrefix(IdlType type) {
		if (type instanceof Boolean) {
			return "Boolean_";
		}
		else if (type instanceof Identifiable)
			return ((Identifiable)type).getName() + "_";
		else
			return getAdaType(type) + "_";
	}

}
